/*
Copyright (C) 1996-1997 GX Media, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "stdafx.h"
#include "LCommon.h"
#include "Qoole.h"
#include "QDraw.h"

// ========== Common ==========

void
LError(const wxChar *fmt, ...) {
	va_list arglist;
	wxString buf;
	
	if(fmt) {
		va_start(arglist, fmt);
		buf.PrintfV(fmt, arglist);
		va_end(arglist);
	}

	fprintf(stderr, "Error: %s\n", (const char*)buf.utf8_str());
	wxMessageDialog dlg(NULL, buf,
			wxT(PACKAGE_VERSION_NAME " Error"),
			wxSTAY_ON_TOP|wxOK|wxCENTRE);
	dlg.ShowModal();
}

void
LFatal(const wxChar *fmt, ...) {
	va_list arglist;
	wxString buf;
	
	if(fmt) {
		va_start(arglist, fmt);
		buf.PrintfV(fmt, arglist);
		va_end(arglist);
	}

	fprintf(stderr, "Fatal Error: %s\n", (const char*)buf.utf8_str());
	wxMessageDialog dlg(NULL, buf,
			wxT(PACKAGE_VERSION_NAME " Fatal Error"),
			wxSTAY_ON_TOP|wxOK|wxCENTRE);
	dlg.ShowModal();

	abort();
	//wxGetApp().GetTopWindow()->Close(true);
}

void LWarning(const wxChar *fmt, ...)
{
	va_list arglist;
	wxString buf;

	if(fmt) {
		va_start(arglist, fmt);
		buf.PrintfV(fmt, arglist);
		va_end(arglist);
	}

	fprintf(stdout, "Warning: %s", (const char*)buf.utf8_str());
	QDraw::OutputText(buf);
}

void LTrace(const wxChar *fmt, ...)
{
	va_list arglist;
	wxString buf;

	if(fmt) {
		va_start(arglist, fmt);
		buf.PrintfV(fmt, arglist);
		va_end(arglist);
	}

	fprintf(stdout, "%s", (const char*)buf.utf8_str());
	QDraw::OutputText(buf);
}

// ========== LFile ==========
#if defined(_WIN32)
#include "direct.h"
#elif defined(linux)
#include <sys/stat.h>
#include <dlfcn.h>
#else
#error unsupported platform
#endif

wxString LFile::dirs[32];
int LFile::numdirs = 0;
LPak *LFile::paks[32];
int LFile::numpaks = 0;

LFile::LFile(void) {
	file = NULL;
	pak = NULL;
	lineNum = 0;
}

LFile::~LFile(void) {
	Close();
}

bool
LFile::Exist(const wxString &filename) {
	bool ret;
	LFile file;
	ret = file.Open(filename);
	file.Close();
	return ret;
}

bool
LFile::ExistDir(const wxString &dirname) {
#ifdef _WIN32
	DWORD attr = GetFileAttributesW(dirname.wc_str());
	return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0;
#else
	struct stat s;
	stat(dirname.utf8_str(), &s);
	return S_ISDIR(s.st_mode);
#endif
}

bool
LFile::Open(const wxString &filename, int mode) {
	int i, j;
	char buf[256];

	file = NULL;

	if(filename.empty())
		return false;

	if(mode & LFILE_READ && !(mode & LFILE_FROMPAK))
		file = fopen(filename.utf8_str(), "rb");
	if(mode & LFILE_WRITE)
		file = fopen(filename.utf8_str(), "wb");
	if(mode & LFILE_APPEND)
		file = fopen(filename.utf8_str(), "ab");

	if(!file && !(mode & LFILE_FROMPAK)) {
		for(i = 0; i < numdirs; i++) {
			wxString newFile = wxString::Format(wxT("%s/%s"), dirs[i].c_str(),filename.c_str());
			file = fopen(newFile.utf8_str(), "rb");
			if(file)
				break;
		}
	}

	if(file) {
		fseek(file, 0, SEEK_END);
		fileSize = ftell(file);
		fseek(file, 0, SEEK_SET);
		return true;
	}

	wxString searchName = filename;
	searchName.Replace(wxT("\\"), wxT("/"), true);

	for(i = 0; i < numpaks; i++) {
		pak = paks[i];
		for(j = 0; j < pak->entries; j++) {
			if(searchName.CmpNoCase(wxString(pak->entry[j].filename, wxConvUTF8)) == 0) {
				pakOffset = pak->entry[j].offset;
				fileSize = pak->entry[j].size;
				fileOffset = 0;
				pak->Seek(pakOffset);
				return true;
			}
		}
	}

	return false;
}

void
LFile::Close(void) {
	if(file) {
		fclose(file);
		file = NULL;
	}
}

int
LFile::Size(void) {
	return fileSize;
}

int
LFile::Tell(void) {
	return ftell(file);
}

int
LFile::Seek(int offset) {
	if(file)
		return fseek(file, offset, SEEK_SET);

	if(pak) {
		fileOffset = offset;
		return pak->Seek(pakOffset + fileOffset);
	}

	return -1;
}

int
LFile::SeekCur(int offset) {
	if(file)
		return fseek(file, offset, SEEK_CUR);

	if(pak) {
		fileOffset += offset;
		return pak->Seek(pakOffset + fileOffset);
	}

	return -1;
}

int
LFile::Read(void *buf, int size, int count) {
	if(file)
		return fread(buf, size, count, file);

	if(pak) {
		Seek(fileOffset);
		fileOffset += size * count;
		if(fileOffset > fileSize)
			size -= fileOffset - fileSize;
		return pak->Read(buf, size, count);
	}

	return 0;
}

int
LFile::Write(const void *buf, int size, int count) {
	if(file)
		return fwrite(buf, size, count, file);

	return 0;
}

bool
LFile::EndOfFile(void) {
	if(file)
		return feof(file) != 0;

	if(pak)
		return fileOffset > fileSize;

	return true;
}

char *
LFile::GetLine(void) {
	if(file) {
		char *c;
		for(c = data; isspace(*c); c++);
		return c;
	}

	if(pak)
		return pak->GetLine();

	return NULL;
}

char *
LFile::GetNextLine(void) {
	if(file) {
		if(fgets(data, 1024, file)) {
			lineNum++;
			return GetLine();
		}
		else
			return NULL;
	}

	if(pak) {
		lineNum++;
		pak->Seek(pakOffset + fileOffset);
		char *line = pak->GetNextLine();
		fileOffset = pak->Tell() - pakOffset;
		if(fileOffset > fileSize)
			return NULL;
		return line;
	}

	return NULL;
}

int
LFile::GetLineNumber(void) {
	return lineNum;
}

char *
LFile::Search(const char *pattern) {
	while(GetNextLine())
		if(!strncmp(GetLine(), pattern, strlen(pattern)))
			return GetLine();

 	return NULL;
}

bool
LFile::Extract(const wxString &filename, const wxString &dst) {
	LFile rFile, wFile;

	if(!rFile.Open(filename, LFILE_FROMPAK))
		return false;

	if(!wFile.Open(dst, LFILE_WRITE))
		return false;

	int size = rFile.Size();

	char *buf = new char[size];
	rFile.Read(buf, size);
	int rtn = wFile.Write(buf, size);
	delete buf;

	return (rtn != 0);
}

void
LFile::MakeDir(const wxString &dirname) {
	wxString dir = dirname;
	dir.Replace(wxT("\\"), wxT("/"), true);

	if(ExistDir(dir))
		return;

	size_t newPos = 0;
	size_t pos = 0;
	while(true) {
		newPos = dir.find(wxT('/'), pos);
		if(newPos == pos)
			break;

		wxString subdir = dir.substr(0, newPos);
#ifdef _WIN32
		mkdir(subdir.utf8_str());
#else
		mkdir(subdir.utf8_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
#endif
		pos = newPos+1;
	}

#ifdef _WIN32
		mkdir(dir.utf8_str());
#else
		mkdir(dir.utf8_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
#endif
}

void
LFile::UseDir(const wxString &dirname) {
	char cwd[MAX_PATH] = "";

	if(dirname.empty())
		return;

#ifdef _WIN32
	if(dirname.find(wxT(":")) == wxString::npos)
	{
		_getcwd(cwd, MAX_PATH);
		strcat(cwd, "/");
	}
#else
	if(dirname[0] != '/')
	{
		if(!getcwd(cwd, MAX_PATH))
			strcpy(cwd, ".");
		strcat(cwd, "/");
	}
#endif

	wxString newDir = wxString(cwd, wxConvUTF8) + dirname;

	for(int i = 0; i < numdirs; i++)
		if(dirs[i] == newDir)
			return;

	ASSERT(numdirs < MaxSearchDirs);
	dirs[numdirs++] = newDir;
}

LPak *
LFile::UsePak(const wxString &filename) {
	if(!Exist(filename))
		return NULL;

	for(int i = 0; i < numpaks; i++)
		if(paks[i]->filename == filename)
			return paks[i];

	paks[numpaks++] = new LPak(filename);

	return paks[numpaks - 1];
}

wxString LFile::initDir = wxT("");

void
LFile::Init(void) {
	char buffer[MAX_PATH];
#ifdef _WIN32
#ifdef _DEBUG
	_getcwd(buffer, 255);
	initDir = wxString(buffer, wxConvUTF8);
#else
	GetModuleFileNameA(GetModuleHandle(NULL), buffer, 255);
	char *c = strrchr(buffer, '\\');
	if(c)
		*c = 0;
	initDir = wxString(buffer, wxConvUTF8);
#endif
#else
	if(!getcwd(buffer, MAX_PATH))
		initDir = wxT(".");
	else
		initDir = wxString(buffer, wxConvUTF8);
#endif
}

void
LFile::Exit(void) {
	int i;
	
	for(i = 0; i < numpaks; i++)
		delete paks[i];

	numdirs = 0;
	numpaks = 0;
}

wxString LFile::GetModifyTime(const wxString &fileName) {
	if (!Exist(fileName))
		return wxT("");

#ifdef _WIN32
    struct _finddata_t c_file;
	_findfirst(fileName.utf8_str(), &c_file);

	return wxString(ctime(&c_file.time_write), wxConvUTF8);
#else
	struct stat s;
	stat(fileName.utf8_str(), &s);

	return wxString(ctime(&s.st_mtime), wxConvUTF8);
#endif
}

// ========== LPak ==========

LPak::LPak(const wxString &_filename)
	: filename(_filename)
{
	Open(filename);

	Read(&header, sizeof(header), 1);
	Seek(header.offset);

	if(!strncmp(header.magic, "SPAK", 4)) {
		int tmpEntries = header.size / sizeof(pak_entry_t);
		pak_entry_t *tmpEntry = new pak_entry_t[tmpEntries];

		Read(tmpEntry, sizeof(pak_entry_t), tmpEntries);

		entries = tmpEntries / 2;
		entry = new pak_entry_t[entries];

		for(int i = 0; i < entries; i++) {
			entry[i] = tmpEntry[i * 2];
			entry[i].offset = tmpEntry[i * 2 + 1].offset;
			entry[i].size = tmpEntry[i * 2 + 1].size;
		}

		delete [] tmpEntry;

		return;
	}

	entries = header.size / sizeof(pak_entry_t);
	entry = new pak_entry_t[entries];

	Read(entry, sizeof(pak_entry_t), entries);
}

LPak::~LPak(void) {
	Close();
	delete [] entry;
}


// ========== LFindFiles ==========

LFindFiles::LFindFiles(const wxString &rootdir, const wxString &filemask)
  : rootdir(rootdir)
{
	if(!rootdir.empty())
		name = rootdir + wxT("/") + filemask;
	else
		name = filemask;

	// Check for a trailing '/'. Used in some platforms.
	/*size_t len = strlen(name);
	if(name[len-1] != '/')
	{
		name[len] = '/';
		name[len+1] = 0;
	}*/

	rc = 0;
}

LFindFiles::~LFindFiles()
{
#ifndef _WIN32
	if(rc)
	{
		globfree(&globResult);
	}
#endif
}

wxString
LFindFiles::Next(void) {
#ifdef _WIN32
	if(!rc) {
		if((rc = _findfirst(name.utf8_str(), &fileinfo)) == -1L)
			return wxT("");
	}
	else {
		if(_findnext(rc, &fileinfo) != 0L)
			return wxT("");
	}
	if(!(fileinfo.attrib & _A_SUBDIR) && strcmp(fileinfo.name, ".") && strcmp(fileinfo.name, ".."))
		return rootdir + wxT("/") + wxString(fileinfo.name, wxConvUTF8);
	else
		return Next();

#else
	char temp[256];
	struct stat s;

	if(!rc)
	{
		memset(&globResult, 0, sizeof(glob_t));
		rc = glob(name.utf8_str(), GLOB_TILDE, NULL, &globResult);
		if(rc == 0)
			rc = 1;
		nextPos = 0;
	}
	else
	{
		nextPos++;
	}

	// Try to read the matched element.
	if(nextPos >= globResult.gl_pathc)
	{
		return wxT("");
	}
	else
	{
		// Perform a stat.
		stat(globResult.gl_pathv[nextPos], &s);
		if(!S_ISDIR(s.st_mode))
			return wxString(globResult.gl_pathv[nextPos], wxConvUTF8);
	}

	return Next();
#endif
}


// ========== LFindDirs ==========

LFindDirs::LFindDirs(const wxString &rootdir) {
#ifdef _WIN32
	if(!rootdir.empty())
		name = rootdir + wxT("/*.*");
	else
		name = wxT("*.*");
#else
	name = rootdir;
#endif
	rc = 0;

	// Check for a trailing '/'. Used in some platforms.
	size_t len = name.size();
	if(name[len-1] != wxT('/'))
		name = name + wxT("/");
}

wxString
LFindDirs::Next(void) {
#ifdef _WIN32
	if(!rc) {
		if((rc = _findfirst(name.utf8_str(), &fileinfo)) == -1L)
			return wxT("");
	}
	else {
		if(_findnext(rc, &fileinfo) != 0L)
			return wxT("");
	}
	if(fileinfo.attrib & _A_SUBDIR && strcmp(fileinfo.name, ".."))
		return wxString(fileinfo.name, wxConvUTF8);
	else
		return Next();

#else
	char temp[256];
	struct stat s;

	if(!rc)
	{
		dir = opendir(name.utf8_str());
		rc = dir != NULL;
		if(!dir)
			return wxT("");
	}

	// Read the entry.
	dirent = readdir(dir);
	if(!dirent)
		return wxT("");

	// Perform a stat.
	sprintf(temp, "%s%s", (const char *)name.utf8_str(), dirent->d_name);
	stat(temp, &s);

	// Only return directories, but not "..".
	if(S_ISDIR(s.st_mode) && strcmp(dirent->d_name, ".."))
		return wxString(dirent->d_name, wxConvUTF8);
	return Next();
#endif
}


// ========== LConfig ==========
int LConfig::currentVer = 0;
int LConfig::requiredVer = 0;
int LConfig::registryVer = 0;

LConfig::LConfig(const wxString &name) {
	sectionName = name;
}

LConfig::~LConfig(void) {
	for(int i = 0; i < lvars.size(); i++)
		delete lvars[i];
}

void
LConfig::RegisterVar(const wxString &name, void *ptr, int type) {
	LVar *lvar = new LVar;
	lvars.push_back(lvar);

	lvar->name = name;
	lvar->ptr = ptr;
	lvar->type = type;

	if (registryVer < requiredVer)
		return;

	QooleApp &theApp = wxGetApp();
	wxString str = theApp.GetProfileString(
			wxString(sectionName, wxConvUTF8),
			wxString(lvar->name,wxConvUTF8));

	if(str.empty())
		return;

	char buf[256];
	strcpy(buf, str.char_str(wxConvUTF8));

	if(lvar->type == LVAR_STR)
		strcpy((char *)lvar->ptr, buf);
	else if(lvar->type == LVAR_INT)
		sscanf(buf, "%d", (int*)lvar->ptr);
	else if(lvar->type == LVAR_FLOAT)
		sscanf(buf, "%f", (float*)lvar->ptr);
	else if(lvar->type ==LVAR_WXSTR)
		*(wxString*)lvar->ptr = str;
}

void
LConfig::SaveVars(void) {
	char buffer[64];
	QooleApp &theApp = wxGetApp();
	wxString value;

	for(int i = 0; i < lvars.size(); i++) {
		LVar *lvar = lvars[i];

		if(lvar->type == LVAR_STR)
			value = wxString((const char *)lvar->ptr, wxConvUTF8);
		else if(lvar->type == LVAR_INT)
			value = wxString::Format(wxT("%d"), *(int *)lvar->ptr);
		else if(lvar->type == LVAR_FLOAT)
			value = wxString::Format(wxT("%f"), *(float *)lvar->ptr);
		else if(lvar->type == LVAR_WXSTR)
			value = *(wxString*)lvar->ptr;

		theApp.WriteProfileString(wxString(sectionName, wxConvUTF8),
				lvar->name,
				value);
	}
}

void LConfig::Init(int currentVersion, int requiredVersion) {
	currentVer = currentVersion;
	requiredVer = requiredVersion;

	QooleApp &theApp = wxGetApp();
	registryVer = theApp.GetProfileInt(wxT("_LConfig"), wxT("Version"), 0);
}

void LConfig::Exit(void) {
	QooleApp &theApp = wxGetApp();
	theApp.WriteProfileInt(wxT("_LConfig"), wxT("Version"), currentVer);
}

// ========== LPalette ==========

LPalette::LPalette() {
}

LPalette::~LPalette() {
}

void LPalette::Load(const wxString &filename, float gamma) {
	LFile file;
	if(!file.Open(filename))
		return;

	unsigned char buf[768];

	int i, c = 0;
	file.Read(buf, 768);
	file.Close();
	for(i = 0; i < 256; i++) {
		int r, g, b;
		r = buf[c++];
		g = buf[c++];
		b = buf[c++];
		GammaCorrect(r, g, b, gamma);
		pal[i].red = r;
		pal[i].green = g;
		pal[i].blue = b;
	}
}

void LPalette::GammaCorrect(int &r, int &g, int &b, float gamma) {
	if(r == 0 && g == 0 && b == 0) return;
	r = (int)((float)r - gamma * 100.0f + 100.0f);
	g = (int)((float)g - gamma * 100.0f + 100.0f);
	b = (int)((float)b - gamma * 100.0f + 100.0f);
	if(r < 0) r = 0;
	if(g < 0) g = 0;
	if(b < 0) b = 0;
	if(r > 255) r = 255;
	if(g > 255) g = 255;
	if(b > 255) b = 255;
}


// ========== LLibrary ==========

LLibrary::Libraries LLibrary::libs;

LLibrary::LLibrary(const wxString &libName) {
#ifdef _WIN32
	module = (void*)LoadLibraryW(libName.wc_str());
#else
	module = dlopen(libName.utf8_str(), RTLD_GLOBAL);
#endif
	name = libName;
}

LLibrary::~LLibrary() {
#ifdef _WIN32
	FreeLibrary((HINSTANCE)module);
#else
	dlclose(module);
#endif
}

void *LLibrary::GetSymbol(const wxString &sym)
{
#ifdef _WIN32
	return (void*)GetProcAddress((HINSTANCE)module, sym.utf8_str());
#else
	return dlsym(module, sym.utf8_str());
#endif
}

LLibrary *LLibrary::FindLibrary(const wxString &libName) {

	Libraries::const_iterator it = libs.begin();
	for(; it != libs.end(); it++)
	{
		LLibrary *lib = *it;
		if(lib->name == libName)
			return lib;
	}

	LLibrary *ret = new LLibrary(libName);
	libs.push_back(ret);
	return ret;
}


// ========== Print Formatting ==========

#include "string.h"
wxString
FormatFloat(float number) {
  char rtnVal[32];
  int i, len;

  // Strip the trailing 0's
  sprintf(rtnVal, "%f", number);
  len = strlen(rtnVal);
  for(i = len - 1;
      i > 0 && rtnVal[i] == '0' && rtnVal[i - 1] != '.';
      i--) {
    rtnVal[i] = '\0';
  }

  return wxString(rtnVal, wxConvUTF8);
}


//========== Conversion Stuff ==========
bool Str2Int(const wxString &str, unsigned int &val)  {
	if (str.empty())
		return false;

	val = 0;
	for(size_t i = 0; i < str.size(); i++) {
		char c = (char)str[i];
		if (!isdigit(c))
			return false;
		val = val*10 + c - '0';
	}
	return true;
}

// ========== Time Code ==========

#include <sys/timeb.h>

int start_sec;
int start_msec;

#ifdef _WIN32
#define timeb _timeb
#define ftime _ftime
#endif

void ReadTime(int *sec, int *msec) {
    struct timeb t;
    ftime(&t);
	*sec = (int)t.time;
	*msec = (int)t.millitm;
}

int GetTime(void) {
	int sec, msec;

	static bool init = false;
	if(!init) {
		ReadTime(&start_sec, &start_msec);
		init = true;
	}

	ReadTime(&sec, &msec);
	return (sec - start_sec) * 1000 + (msec - start_msec);
}


// ========== Old VOS Stuff ==========
// TODO: fix/replace this stuff up

int String_Split(substr_t substr[], char *org_string, int max) {
	char *ch, last_ch;
	char start_ch;
	char string[512];
	int num, pos;
	int quoting = false;

	strcpy(string, org_string);

	for(num = 0; num < max; num++)
		memset(substr[num], 0, sizeof(substr_t));

	// turn tabs to spaces
	ch = string;
	while(*ch != '\0') {
		if(*ch == '\t')
			*ch = ' ';
		ch++;
	}

	String_Crop(string);

	last_ch = '\0';
	ch = string;
	num = 0;
	pos = 0;

	while(true) {
		if(*ch == '\"' || (*ch == '\'' && !quoting) || (*ch == '\'' && *ch == start_ch)) {
			quoting = abs(quoting - 1);
			if(quoting)
				start_ch = *ch;
			if(num < max - 1) {
				last_ch = *ch++;
				continue;
			}
		}

		if(*ch == '\0')
			return num + 1;

		if(*ch != ' ') {
			strncat(substr[num], ch, 1);
			pos++;

			last_ch = *ch++;
			continue;
		}

		if(*ch == ' ' && last_ch != ' ') {
			if(quoting) {
				 strncat(substr[num], ch, 1);
				 pos++;
			}
			else {
				if(num < max - 1) {
					num++;
				}
				else {
					if(*(ch + 1) != ' ') {
						strncat(substr[num], ch, 1);
						pos++;
					}
				}
			}
		}

		if(*ch == ' ' && last_ch == ' ' && quoting) {
			strncat(substr[num], ch, 1);
			pos++;
		}

		if(pos > sizeof(substr_t)) {
			pos = 0;
			num++;
			if(num == max)
				return max;
		}

		last_ch = *ch++;
	}

}

// removing beginning and trailing whitespaces
void String_Crop(char *str) {
	char *c = str;
	while(*c != '\0' && (*c == ' ' || *c == '\t'))
		c++;
	strcpy(str, c);

	c = str + strlen(str) - 1;
	while(c > str && (*c == ' ' || *c == '\t'))
		c--;

	c++;
	if(*c)
		*c = '\0';

	if(str[strlen(str) - 1] == '\n')
		String_Chop(str);
	if(str[strlen(str) - 1] == '\r')
		String_Chop(str);
}

wxString String_Crop(const wxString &s)
{
	if(s.empty())
		return s;

	size_t len = s.size();
	size_t start = 0;
	size_t end = len;

	// Find the start point.
	for(size_t i = 0; i < len; i++)
	{
		if(s[i] <= ' ')
			continue;
		start = i;
		break;
	}

	// Find the end point
	for(size_t i = start; i < len; i++)
	{
		if(s[i] <= ' ')
			continue;

		end = i+1;
	}

	return s.substr(start, end - start);
}

void String_Chop(char *str) {
	int i = strlen(str);
	if(i)
		str[i - 1] = '\0';
}

wxString String_Chop(const wxString &s)
{
	if(s.empty())
		return s;

	size_t len = s.size();
	size_t end = len;
	for(size_t i = len-1; i >= 0; i--)
	{
		if(s[i] == '\r' || s[i] == '\n')
			end = i;
		else
			break;

		if(i == 0)
			break;
	}

	if(end == 0)
		return wxEmptyString;

	return s.substr(0, end);
}

void String_CutAt(char *str, const char *at) {
	char *c = strstr(str, at);
	if(c)
		*c = '\0';
}

int String_Count(const char *str, char ch) {
	int i = 0;
	const char *c = str;
	while(*c)
		if(*c++ == ch)
			i++;
	return i;
}

#ifndef _WIN32
void strlwr(char *buf)
{
	while(*buf != 0)
	{
		if(*buf >= 'A' && *buf <= 'Z')
			*buf += 'a' - 'A';
		buf++;
	}
}

void strupr(char *buf)
{
	while(*buf != 0)
	{
		if(*buf >= 'a' && *buf <= 'z')
			*buf -= 'a' - 'A';
		buf++;
	}
}

#endif

int strstri(const char *str1, const char *str2) {
	int result;
	char *buf1 = (char *)malloc(strlen(str1) + 1);
	char *buf2 = (char *)malloc(strlen(str2) + 1);
	strcpy(buf1, str1);
	strcpy(buf2, str2);
	strupr(buf1);
	strupr(buf2);
	result = (size_t)strstr(buf1, buf2) - (size_t)buf1;
	free(buf1);
	free(buf2);
	return result;
}


sort_t *lmerge(sort_t *p, sort_t *q) {
	sort_t *r, head;

	for(r = &head; p && q; ) {
		if (p->key < q->key) {
			r = r->next = p;
			p = p->next;
		}
		else {
			r = r->next = q;
			q = q->next;
		}
	}
	r->next = (p ? p : q);
	return head.next;
}

sort_t *lsort(sort_t *p) {
	sort_t *q, *r;

	if(p) {
		q = p;
		for(r = q->next; r && (r = r->next) != NULL; r = r->next) { q = q->next; }
		r = q->next;
		q->next = NULL;
		if(r) { p = lmerge(lsort(r), lsort(p)); }
	}
	return p;
}

struct pcx_header_t {
	char manufacturer;
	char version;
	char encoding;
	char bits_per_pixel;
	short xmin, ymin;
	short xmax, ymax;
	short hres, vres;
	char palette[48];
	char reserved;
	char color_planes;
	short btyes_per_line;
	short palette_type;
	char filler[58];
};

void SavePCX(const wxString &name, unsigned char *surface, int width, int height, lpal_t *pal) {

	wxString filename = name;
	if(filename.substr(filename.rfind(wxT("."))) != wxT(".pcx"))
		filename = name + wxT(".pcx");

	LFile file;
	file.Open(filename, LFILE_WRITE);

	pcx_header_t pcx_header;

	pcx_header.manufacturer = 10;
	pcx_header.version = 5;
	pcx_header.encoding = 1;
	pcx_header.bits_per_pixel = 8;
	pcx_header.xmin = 0;
	pcx_header.ymin = 0;
	pcx_header.xmax = (short)width - 1;
	pcx_header.ymax = (short)height - 1;
	pcx_header.hres = 100;
	pcx_header.vres = 100;
	pcx_header.color_planes = 1;
	pcx_header.btyes_per_line = (short)width;
	pcx_header.palette_type = 1;

	file.Write(&pcx_header, sizeof(pcx_header_t));
	
	unsigned char *rle = (unsigned char *)malloc(width * height * 2 + 1);
	unsigned char *r = rle;
	unsigned char *s = surface;

	for(int i = 0; i < width * height; i++) {
		*r++ = 193;
		*r++ = *s++;
	}

	file.Write(rle, width * height * 2);
	file.Write(pal, 768);

	free(rle);
}

int NearestGreaterPower2(int x) {
	int i = 0;
	while(1) {
		if(x <= 1 << i)
			return 1 << i;
		i++;
	}
	return 1;
}


